package com.ezops.util;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

import org.hibernate.CacheMode;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/*****
 * Thread Local Storage Hibernate Session and Transaction context
 * Used in every api or method call access the hibernate DAO layer with a session and optional jpa transaction 
 * object 
 * @author HindsC
 *
 */
public class HibernateSessionTransactionTLSContext {
	
      final static Logger logger = LoggerFactory.getLogger(ApplicationContextUtils.class);

	   private static final ThreadLocal threadSession = new ThreadLocal();
	   
	   private static final ThreadLocal threadTransaction = new ThreadLocal();
	   
	   private SessionFactory sessionFactory;

	   
	   /****
	    * Set the Hibernate SessionFactory Here
	    * @param sessionFactory
	    */
	   public void setSessionFactory(SessionFactory sessionFactory){
		   
		   this.sessionFactory = sessionFactory;
		   
	   }
	   
	   /***
	    * Get Current Hibernate Session form the factory and set it in the 
	    * ThreadLocalStorage field or just get it from TLS.
	    * @return
	    */
	   public Session getCurrentSession() {
	   
		Session s = (Session)threadSession.get();
	      try {
	         if (s == null || !s.isOpen()) {
	        	 
	            if (logger.isInfoEnabled()) {
	            	
	            	logger.info("Opening new Session for this thread.");
	            }
	            
	            s = sessionFactory.openSession();
	            s.setCacheMode(CacheMode.NORMAL);
	            
	            threadSession.set(s);
	         }
	         else {
	        	 
	            if (logger.isInfoEnabled()) {
	            	
	            	logger.info("Using current session in this thread.");
	            }
	         }
	      }
	      catch (HibernateException ex) {
	    	 
	    	//Log the exception
  			ByteArrayOutputStream bs = new ByteArrayOutputStream();
     		PrintStream ps = new PrintStream(bs);
     		ex.printStackTrace(ps);
  			logger.error(bs.toString());
	         throw ex ;
	         
	      }
	      return s;
	   }

	   /***
	    * Close the current Session in TLS
	    */
	   public void closeSession() {
	      try {
	         final Session s = (Session)threadSession.get();
	         
	         if (s != null && s.isOpen()) {
	        	 
	           logger.info("Closing Session of this thread.");
	            s.close();
	         }
	      }
	      catch (HibernateException ex) {
	   
    	  	//Log the exception
  			ByteArrayOutputStream bs = new ByteArrayOutputStream();
     		PrintStream ps = new PrintStream(bs);
     		ex.printStackTrace(ps);
  			logger.error(bs.toString());
	      
	      }
	      finally {
	         threadSession.set(null);
	      }
	   }

	   /***
	    * Begin a new transaction 
	    */
	   public  void beginTransaction() {
	      Transaction tx = (Transaction)threadTransaction.get();
	      try {
	         if (tx != null ) {
	            tx = null;
	            threadTransaction.set(null);
	         }
	         if (tx == null) {
	        	 
	            if (logger.isInfoEnabled()) {
	               
	            	logger.info("Starting new database transaction in this thread.");
	            }
	            if (threadSession.get() != null && ((Session)threadSession.get()).isOpen()) {
	               
	            	((Session)threadSession.get()).close();
	                 threadSession.set(null);
	            }
	            
	            tx = (Transaction)getCurrentSession().beginTransaction();
	            
	            	threadTransaction.set(tx);
	            
	         }
	         else {
	        	 
	            if (logger.isInfoEnabled()) {
	            	logger.info("Using current database transaction in this thread.");
	            	logger.info("Opening new Session for this thread.");
	            }
	         }
	      }
	      catch (HibernateException ex) {
	    	  
	    	 	//Log the exception
	  			ByteArrayOutputStream bs = new ByteArrayOutputStream();
	     		PrintStream ps = new PrintStream(bs);
	     		ex.printStackTrace(ps);
	  			logger.error(bs.toString());
	      }
	      finally {
	    	  
	         if (threadSession.get() == null || ! ((Session)threadSession.get()).isOpen()) {
	        	 
	            getCurrentSession();
	         }
	         else {
	        	 
	        	 ((Session)threadSession.get()).clear();
	         }
	      }
	   }

	   /***
	    * Commit the transaction
	    */
	   public void commitTransaction() {
	      final Transaction tx = (Transaction)threadTransaction.get();
	      try {
	         if (tx != null)
	         {
	            Session s = getCurrentSession();
	            s.flush();
	            if (logger.isInfoEnabled()) {
	               logger.info("Flushing session and committing transaction of this thread.");
	            }
	            
	            
	            tx.commit();
	         }
	      }
	      catch (HibernateException ex) {
	    	  
	         rollbackTransaction();
	         
	       //Log the exception
	  		 ByteArrayOutputStream bs = new ByteArrayOutputStream();
	     	 PrintStream ps = new PrintStream(bs);
	     	 ex.printStackTrace(ps);
	  		logger.error(bs.toString());
	         
	      } catch (SecurityException e) {
	    	 
	    	  //Log the exception
		  	   ByteArrayOutputStream bs = new ByteArrayOutputStream();
		       PrintStream ps = new PrintStream(bs);
		       e.printStackTrace(ps);
		  	 logger.error(bs.toString());
		        	
			
		} finally {
	         threadTransaction.set(null);
	         closeSession();
	      }
	   }

	   /***
	    * Rollback the transaction
	    */
	   public void rollbackTransaction() {
	    
	      final Transaction tx = (Transaction)threadTransaction.get();
	      try {
	    	  
	         if (tx != null ) {
	            logger.info("Trying to rollback database transaction of this thread.");
	            tx.rollback();
	         }
	         
	      }
	      catch (HibernateException ex) {
	    	  
	    	//Log the exception
		  	 ByteArrayOutputStream bs = new ByteArrayOutputStream();
		     PrintStream ps = new PrintStream(bs);
		     ex.printStackTrace(ps);
		  	logger.error(bs.toString());
		  	
	      } catch (IllegalStateException e) {
	    	 	//Log the exception
			  	 ByteArrayOutputStream bs = new ByteArrayOutputStream();
			     PrintStream ps = new PrintStream(bs);
			     e.printStackTrace(ps);
			  	logger.error(bs.toString());
				
		 
		  } finally {
	         threadTransaction.set(null);
	         closeSession();
	      }
	   }

	/**
	 * @return the mSessionFactory
	 */
	public SessionFactory getmSessionFactory() {
		return sessionFactory;
	}

	/**
	 * @param mSessionFactory the mSessionFactory to set
	 */
	public void setmSessionFactory(SessionFactory mSessionFactory) {
		sessionFactory = mSessionFactory;
	}
	

}
